package cn.linran.basic.service.impl;

import cn.linran.basic.constant.RegisterConstant;
import cn.linran.basic.exception.BusinessException;
import cn.linran.basic.service.IVerifyCodeService;
import cn.linran.basic.util.StrUtils;
import cn.linran.basic.util.VerifyCodeUtils;
import cn.linran.user.domain.User;
import cn.linran.user.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * 图形验证码，验证成功后才允许发送短信验证码
 * AOF 持久化记录服务器执行的所有写操作命令，并在服务器启动时，通过重新执行这些命令来还原数据集，默认关闭该模式
 * 使AOF生效：在windows.config中配置appendonly yes   #yes 开启,no 关闭
 */
@Service
public class VerifyCodeServiceImpl implements IVerifyCodeService {
    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private UserMapper userMapper;

    /**
     * 生成可以转化成图片的base64位字符串
     * @param key 由前端uuid生成的唯一key，在后端redis中存储4位图片验证码
     * @return base64位字符串
     */
    @Override
    public String getVerifyCode(String key) {
        // 1.校验一波
        if(StringUtils.isEmpty(key))
            throw new BusinessException("参数有误！");
        // 2. 生成4位随机验证码
        String code = StrUtils.getComplexRandomString(4);//几位就写几
        // 3. 存入redis中（需要启动redis-start），设置过期时间为3分钟。
        redisTemplate.opsForValue().set(key,code,3, TimeUnit.MINUTES);
        // 4. 生成一个base64位字符串
        return VerifyCodeUtils.verifyCode(100,40,code);
    }

    /**（获取按钮）
     * 首先判断输入的图形验证码是否正确，如果正确发送短信验证码，不正确重新输入
     * @param map
     */
    @Override
    public void smsCode(Map<String, String> map) {
        String phone = map.get("phone");  // 取出前端用户输入的手机号
        String imageCode = map.get("imageCode");  // 取出前端用户输入的图形验证码
        String imageCodeKey = map.get("imageCodeKey"); // 和getVerifyCode(key)中的形参key是同一个值，前端uuid随机生成唯一的key，存于redis时当作key,value为getVerifyCode2中的4位验证码（对应前端发送数据：register.html-sendMobileCode3.1）
        // 1. 校验，参数不能为空
        if(StringUtils.isEmpty(phone)||
                StringUtils.isEmpty(imageCode)||
                StringUtils.isEmpty(imageCodeKey)
        ){
            throw new BusinessException("参数不能为空");
        }
        // 2. 图形验证码校验
        Object obj = redisTemplate.opsForValue().get(imageCodeKey);//obj即为redis存储的验证码
        if(obj==null){//如果取不到验证码
            throw new BusinessException("图形验证码过期了，请重新获取输入！");//前端点击图片，后端getVerifyCode处理
        }
        // 验证码不区分大小写
        if(!obj.toString().equalsIgnoreCase(imageCode)){
            throw new BusinessException("图形验证码输入错误，请重新输入！");
        }
        // 3. 手机号是否被注册过 查询user,
        User user = userMapper.findByPhone(phone);
        if(user!=null){  // 查到了
            throw new BusinessException("手机号已经被注册，请直接登录！");
        }

        /**
         * key: 手机号+业务键（见下4.2.3）   业务键：，业务主键一般是按照规则生成的定长的字符串。此处为常量，中间不分开直接拼接
         * value: code:时间戳       code即随机生成的6位短信验证码，时间戳即生成验证码的时间(ms),中间冒号分开
         */
        // 4. 定义code，用来存储随机生成的6为验证码
        String code = null;
        // 4.0 value: code:时间戳  从redis中取
        Object value = redisTemplate.opsForValue().get(phone + RegisterConstant.PHONEREGISTER);//（第一次/验证码超过3分钟）从redis中取出code：时间戳:

        // 4.1. 如果取不到/没有发送过/获取过期了，
        if(value==null){ // 第一次进入，没取到/验证码过期直接生成一个新的验证码保存到redis
            code = StrUtils.getComplexRandomString(6);//（第一次/验证码超过3分钟）生成6位短信验证码
        }else{
            // 4.2. 如果取到了。判断是否超过一分钟。
            String redisTime = value.toString().split(":")[1];//将value后半部分时间戳取出来即：134123412
            long lastTime = Long.valueOf(redisTime);//转成Long类型用于减法
            // 4.2.1： 如果没超过1分钟，直接提示用户。（网络繁忙）
            if(System.currentTimeMillis()-lastTime<1*60*1000){
                throw new BusinessException("一分钟之内不能发送多次！");

            }else{
                // 4.2.2： 如果超过了一分钟，将值重新赋给code，生命周期为3分钟见4.2.6
                code = value.toString().split(":")[0];
            }
        }

        // 4.2.3： 把code存入redis中
        redisTemplate.opsForValue().set(//（第一次）
                phone+ RegisterConstant.PHONEREGISTER,  //4.2.4（第一次）key: 15096190858phoneRegister
                code+":"+System.currentTimeMillis(),    //4.2.5（第一次/验证码超过3分钟）value: abcdef:134123412
                3,TimeUnit.MINUTES);                    //4.2.6（第一次/验证码超过3分钟）3分钟过期


        // 5. 发送短信给用户
        // SmsSendUtil.smsSend(phone,"您的验证码为："+code+",请在三分钟之内使用");//发送给手机
        System.out.println("您的验证码为："+code+",请在三分钟之内使用");//控制台测试
    }

    /**
     * 微信绑定，发送手机短信验证码
     * @param map
     */
    @Override
    public void binderSmsCode(Map<String, String> map) {
        String phone = map.get("phone");  // 手机号

        // 1. 校验，参数不能为空
        if(StringUtils.isEmpty(phone)){
            throw new BusinessException("手机号不能为空");
        }

        /**
         * key: 手机号+微信绑定业务键   业务键：最好是常量
         * value: code:时间戳    code为随机生成的手机验证码  时间戳为生成验证码的当前毫秒数
         *
         *
         */
        // 4. 判断是否是第一次发送请求，或者是验证码过期  从redis中取
        Object value = redisTemplate.opsForValue().get(phone + RegisterConstant.WXBINDER);
        //
        String code = null;
        // 4.1. 如果取不到，没有发送过，获取过期了， 直接生成一个新的验证码保存到redis
        if(value==null){ // 没取到
            code = StrUtils.getComplexRandomString(6);
        }else{
            // 4.2. 如果取到了。判断是否超过一分钟。
            String redisTime = value.toString().split(":")[1];
            long ltime = Long.valueOf(redisTime);
            // 4.2.1： 如果没超过1分钟，直接提示用户，1分钟之内不能多次发送。
            if(System.currentTimeMillis()-ltime<1*60*1000){
                throw new BusinessException("一分钟只能不能发送多次！");

            }else{
                // 4.2.2： 如果超过了一分钟，刷新过期时间，
                code = value.toString().split(":")[0];
            }
        }

        // 4.2.2： 把code存入redisz中，生命周期3分钟
        redisTemplate.opsForValue().set(
                phone+RegisterConstant.WXBINDER,
                code+":"+System.currentTimeMillis(),
                3,
                TimeUnit.MINUTES);

        // 5. 发送短信给用户
        // SmsSendUtil.smsSend(phone,"您的验证码为："+code+",请在三分钟之内使用");
        System.out.println("您的验证码为："+code+",请在三分钟之内使用");
    }
}
